package org.six11.ss.client.olive;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.jdom.Element;
import org.six11.ss.client.ClientStateNames;
import org.six11.util.Debug;
import org.six11.util.adt.MultiState;
import org.six11.util.adt.SynchronizedQueue;
import org.six11.util.pen.Pt;
import org.six11.util.pen.Sequence;

/**
 * 
 * 
 * @author Gabe Johnson <johnsogg@cmu.edu>
 * 
 */
public class OliveState {

  // This local client's programState.getString(MessageNames.PARAM_USER_ID) ID.
  // private String programState.getString(MessageNames.PARAM_USER_ID);

  private MultiState multiState;

  // This is a list of all the strokes known to this client. Some strokes may be 'in progress' (see
  // the currentSequences member).
  private List<Sequence> sequences;

  // this maps programState.getString(MessageNames.PARAM_USER_ID) IDs with sequences they are
  // currently drawing. The referenced Sequence object is
  // also in the 'sequences' list.
  private Map<String, Sequence> currentSequences;
  private List<ChangeListener> changeListeners;

  // For 'special' change listeners that only fire in certain circumstances, like when the return
  // value of 'hasData' will change.
  private Map<String, List<ChangeListener>> specialChangeListeners;

  // a constant change event. Listeners only need to know that something changed so we can safely
  // reuse this and avoid unnecessary object creation.
  private static final ChangeEvent CHANGE_EVENT = new ChangeEvent("");

  // a name for special change listeners interested in knowing when the canvas becomes (or is no
  // longer) empty.
  public static final String EMPTY = "empty";

  public OliveState(MultiState programState) {
    this.multiState = programState;
    changeListeners = new ArrayList<ChangeListener>();
    sequences = new ArrayList<Sequence>();
    currentSequences = new HashMap<String, Sequence>();
  }

  public boolean hasData() {
    return sequences.size() > 0;
  }

  public List<Sequence> getSequences() {
    return sequences;
  }

  @SuppressWarnings("unchecked")
  private void transmit(String type, Pt pt) {
    if (multiState.hasValue(ClientStateNames.K_PEN_QUEUE)) {
      SynchronizedQueue<Element> queue = (SynchronizedQueue<Element>) multiState
          .getValue(ClientStateNames.K_PEN_QUEUE);
      Element elm = new Element("pen_data");
      elm.setAttribute("type", type);
      elm.setAttribute("x", "" + pt.ix());
      elm.setAttribute("y", "" + pt.iy());
      elm.setAttribute("user_time", pt.getTime() + "");
      queue.add(elm);
    } else {
      bug("pen transmission not enabled");
    }
  }

  public void addLocalSequenceBegin(Pt pt) {
    boolean hasDataAtBeginning = hasData();
    Sequence seq = new Sequence();
    sequences.add(seq);
    seq.add(pt);

    currentSequences.put(multiState.getString(ClientStateNames.K_LOCAL_USER), seq);
    transmit("down", pt);
    fireDataChange();
    if (!hasDataAtBeginning) {
      fireSpecialDataChange(EMPTY);
    }
  }

  public void addForeignSequenceBegin(Pt pt, String who) {
    Sequence seq = new Sequence();
    sequences.add(seq);
    seq.add(pt);
    currentSequences.put(who, seq);
    fireDataChange();
  }

  public void addLocalSequenceProgress(Pt pt) {
    currentSequences.get(multiState.getString(ClientStateNames.K_LOCAL_USER)).add(pt);
    transmit("move", pt);
    fireDataChange();
  }

  public void addForeignSequenceProgress(Pt pt, String who) {
    try {
      currentSequences.get(who).add(pt);
      fireDataChange();
    } catch (NullPointerException ex) {
      bug("Adding foreign sequence progress for user " + who
          + " but there was no corresponding 'begin'");
    }
  }

  public void addLocalSequenceEnd(Pt pt) {
    currentSequences.remove(multiState.getString(ClientStateNames.K_LOCAL_USER));
    transmit("up", pt);
    fireDataChange();
  }

  public void addForeignSequenceEnd(@SuppressWarnings("unused") Pt pt, String who) {
    currentSequences.remove(who);
    fireDataChange();
  }

  private void bug(String what) {
    Debug.out("OliveState", what);
  }

  public void addChangeListener(ChangeListener lis) {
    if (!changeListeners.contains(lis)) {
      changeListeners.add(lis);
    }
  }

  public void removeChangeListener(ChangeListener lis) {
    changeListeners.remove(lis);
  }

  public void addChangeListener(String kind, ChangeListener lis) {
    if (specialChangeListeners == null) {
      specialChangeListeners = new HashMap<String, List<ChangeListener>>();
    }
    if (!specialChangeListeners.containsKey(kind)) {
      specialChangeListeners.put(kind, new ArrayList<ChangeListener>());
    }
    specialChangeListeners.get(kind).add(lis);
  }

  public void removeChangeListener(String kind, ChangeListener lis) {
    if (specialChangeListeners != null && specialChangeListeners.get(kind) != null) {
      specialChangeListeners.get(kind).remove(lis);
    }
  }

  private void fireDataChange() {
    for (ChangeListener lis : changeListeners) {
      lis.stateChanged(CHANGE_EVENT);
    }
  }

  private void fireSpecialDataChange(String type) {
    if (specialChangeListeners != null) {
      List<ChangeListener> bunch = specialChangeListeners.get(type);
      if (bunch != null) {
        for (ChangeListener lis : bunch) {
          lis.stateChanged(CHANGE_EVENT);
        }
      }
    }
  }

  public void addForeignSequenceProgress(String otherUserID, Pt pt) {
    if (currentSequences.containsKey(otherUserID) == false) {
      Sequence seq = new Sequence();
      sequences.add(seq);
      currentSequences.put(otherUserID, seq);
    }
    currentSequences.get(otherUserID).add(pt);
    fireDataChange();
  }

  public void addForeignSequenceEnd(String otherUser) {
    currentSequences.remove(otherUser);
    fireDataChange();
  }

  public void clearSurface() {
    sequences.clear();
    currentSequences.clear();
    fireDataChange();
    fireSpecialDataChange(EMPTY);
  }

}
